Project Overview
“Kelmarsh Wind Farm is located near Haselbach, Northamptonshire and comprises six 2.05MW Senvion MM92 turbines. The Project was acquired from EON in December 2014 and was constructed in joint venture with Santander using balance sheet finance. Construction was completed in April 2016. It is now owned by Cubico Investments.”
https://www.blue-energyco.com/our-projects/kelmarsh/
Figure 1: Near Haselbach, Northamptonshire (UK).
Read parquet files
Show the code
```{python}
#| label: Read-Data
tstatus = pl.read_parquet(f"{folder_path_interim}Kelmarsh_Turbines_Status_20160114_20230109_n385133_cols12.parquet")
tstatus
```
shape: (385_133, 12)
datetime[μs, UTC]
datetime[μs, UTC]
str
i64
str
str
str
str
str
str
str
duration[μs]
2016-01-14 19:28:03 UTC
2016-01-23 14:36:32 UTC
"Stop"
111
"Emergency stop nacelle"
null
"Emergency stop switch (Nacelle…
"Forced outage"
"T01"
null
null
8d 19h 8m 29s
2016-01-14 19:28:03 UTC
2016-01-14 19:38:03 UTC
"Warning"
5720
"Brake accumulator defect"
null
"Warnings (27)"
null
"T01"
null
null
10m
2016-01-14 19:28:05 UTC
2016-01-23 11:27:46 UTC
"Informational"
3835
"Cable panel breaker open"
null
"Warnings (27)"
null
"T01"
null
null
8d 15h 59m 41s
2016-01-14 19:28:05 UTC
2016-01-23 11:27:46 UTC
"Informational"
3830
"Supply circuit breaker earthed"
null
"Warnings (27)"
"Full Performance"
"T01"
null
null
8d 15h 59m 41s
2016-01-14 19:28:05 UTC
2016-01-23 14:09:18 UTC
"Warning"
3870
"Overload transformer fan outle…
null
"Warnings (27)"
"Full Performance"
"T01"
null
null
8d 18h 41m 13s
…
…
…
…
…
…
…
…
…
…
…
…
2022-12-31 12:28:30 UTC
null
"Informational"
100160
"System test 3"
null
null
"Technical Standby"
"T06"
null
null
null
2022-12-31 12:28:45 UTC
null
"Informational"
100180
"Run-up"
null
null
"Technical Standby"
"T06"
null
null
null
2022-12-31 12:30:58 UTC
null
"Informational"
100190
"Mains connection"
null
null
"Full Performance"
"T06"
null
null
null
2022-12-31 12:31:08 UTC
null
"Informational"
100200
"Mains run-up"
null
null
"Full Performance"
"T06"
null
null
null
2022-12-31 12:31:38 UTC
null
"Informational"
100210
"Mains operation"
null
null
"Full Performance"
"T06"
null
null
null
Filter Stop
Show the code
```{python}
#| label: Filter-Stops
stops = pl.sql(
"""
SELECT * EXCLUDE ("Custom contract category")
FROM tstatus
WHERE "Status" = 'Stop'
""").collect()
```
Filter Forced Outages
Show the code
```{python}
#| label: Filter-Forced-Outages
# Aggregate tables using SQL syntax
foutages = pl.sql(
"""
SELECT *
FROM stops
WHERE "IEC category" = 'Forced outage'
""").collect()
foutages = foutages.rename({"Duration":"TTR"})
# TTF
foutages = foutages.with_columns(
(pl.col("Timestamp start").shift(-1) - pl.col("Timestamp end")).shift(1).alias("TTF")
)
foutages = foutages.with_columns(
pl.when(pl.col("TTF") < 0).then(None).otherwise(pl.col("TTF")).alias("TTF")
)
foutages
```
shape: (892, 12)
datetime[μs, UTC]
datetime[μs, UTC]
str
i64
str
str
str
str
str
str
duration[μs]
duration[μs]
2016-01-14 19:28:03 UTC
2016-01-23 14:36:32 UTC
"Stop"
111
"Emergency stop nacelle"
null
"Emergency stop switch (Nacelle…
"Forced outage"
"T01"
null
8d 19h 8m 29s
null
2016-01-23 15:05:30 UTC
2016-01-23 15:06:42 UTC
"Stop"
117
"Emergency stop base box"
null
"Emergency stop switch (Convert…
"Forced outage"
"T01"
null
1m 12s
28m 58s
2016-01-24 16:51:17 UTC
2016-02-01 19:46:41 UTC
"Stop"
3110
"Frequency converter error"
null
"Generator and Converter errors…
"Forced outage"
"T01"
null
8d 2h 55m 24s
1d 1h 44m 35s
2016-02-11 14:36:37 UTC
2016-02-11 15:14:50 UTC
"Stop"
3585
"Maximum grid frequency"
null
"External stop (grid) (4)"
"Forced outage"
"T01"
null
38m 13s
9d 18h 49m 56s
2016-03-01 17:33:14 UTC
2016-03-01 17:35:14 UTC
"Stop"
3000
"Frequency converter not ready"
null
"Generator and Converter errors…
"Forced outage"
"T01"
null
2m
19d 2h 18m 24s
…
…
…
…
…
…
…
…
…
…
…
…
2022-05-11 14:08:43 UTC
2022-05-11 14:31:27 UTC
"Stop"
555
"Pitch angle deviation"
null
"Pitch errors (18)"
"Forced outage"
"T06"
"09 (Int) Fault"
22m 44s
2h 55m 14s
2022-05-12 02:37:43 UTC
2022-05-19 11:19:38 UTC
"Stop"
555
"Pitch angle deviation"
"Pitch fault - techs attended o…
"Pitch errors (18)"
"Forced outage"
"T06"
"09 (Int) Fault"
7d 8h 41m 55s
12h 6m 16s
2022-07-11 08:13:14 UTC
2022-07-11 13:48:21 UTC
"Stop"
6530
"Anemometer defect"
"Anemometer failed and replaced"
"Sensor error (21)"
"Forced outage"
"T06"
"09 (Int) Fault"
5h 35m 7s
52d 20h 53m 36s
2022-07-19 13:17:02 UTC
2022-07-19 18:11:27 UTC
"Stop"
3151
"Max.temp.conv.inl.>perm.out.t."
"Converters overheating due to …
"Temperature error (22)"
"Forced outage"
"T06"
"09 (Int) Fault"
4h 54m 25s
7d 23h 28m 41s
2022-11-02 17:39:07 UTC
2022-11-02 18:25:51 UTC
"Stop"
21
"Manual stop - remote"
null
"Remote stop (30)"
"Forced outage"
"T06"
"09 (Int) Fault"
46m 44s
105d 23h 27m 40s
Time Series for Binary Plot
Show the code
```{python}
#| label: Binary-Plot
dtstarts = foutages.select(["Timestamp start", "Status", "Message", "wt"]).rename({"Timestamp start": "Timestamp"})
dtends = foutages.select(["Timestamp end", "Status", "Message", "wt"]).with_columns(pl.lit("Start").alias("Status")).rename({"Timestamp end": "Timestamp"})
tstates = pl.concat([dtstarts, dtends]).sort(["wt", 'Timestamp'])
# tstates_ts = dtstarts.join(dtends, on="Timestamp", how="full").sort(["wt", 'Timestamp'])
tstates= tstates.with_columns(
pl.when(pl.col("Status") == "Stop").then(0).otherwise(1).alias("State"),
pl.col("Timestamp").dt.year().alias("Year")
)
tstates= tstates.with_columns(
(pl.col("wt") + "-" + pl.col("Year").cast(str)).alias("wt_Year")
)
tstates
#TODO: remove missing years!
```
shape: (1_784, 7)
datetime[μs, UTC]
str
str
str
i32
i32
str
2016-01-14 19:28:03 UTC
"Stop"
"Emergency stop nacelle"
"T01"
0
2016
"T01-2016"
2016-01-23 14:36:32 UTC
"Start"
"Emergency stop nacelle"
"T01"
1
2016
"T01-2016"
2016-01-23 15:05:30 UTC
"Stop"
"Emergency stop base box"
"T01"
0
2016
"T01-2016"
2016-01-23 15:06:42 UTC
"Start"
"Emergency stop base box"
"T01"
1
2016
"T01-2016"
2016-01-24 16:51:17 UTC
"Stop"
"Frequency converter error"
"T01"
0
2016
"T01-2016"
…
…
…
…
…
…
…
2022-07-11 13:48:21 UTC
"Start"
"Anemometer defect"
"T06"
1
2022
"T06-2022"
2022-07-19 13:17:02 UTC
"Stop"
"Max.temp.conv.inl.>perm.out.t."
"T06"
0
2022
"T06-2022"
2022-07-19 18:11:27 UTC
"Start"
"Max.temp.conv.inl.>perm.out.t."
"T06"
1
2022
"T06-2022"
2022-11-02 17:39:07 UTC
"Stop"
"Manual stop - remote"
"T06"
0
2022
"T06-2022"
2022-11-02 18:25:51 UTC
"Start"
"Manual stop - remote"
"T06"
1
2022
"T06-2022"
Show the code
```{python}
#| label: Find-Missing-Values
# Get rows containing any null values
missing = (tstates
.with_row_count("row_index")
.filter(pl.any_horizontal(pl.all().is_null()))
.sort("row_index"))
# Display rows with missing values
print("Rows containing missing values:")
print(missing)
# Get count of missing values per column
print("\nMissing value counts per column:")
print(tstates.null_count())
```
Rows containing missing values:
shape: (5, 8)
┌───────────┬───────────────────┬────────┬────────────────────────┬─────┬───────┬──────┬─────────┐
│ row_index ┆ Timestamp ┆ Status ┆ Message ┆ wt ┆ State ┆ Year ┆ wt_Year │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ u32 ┆ datetime[μs, UTC] ┆ str ┆ str ┆ str ┆ i32 ┆ i32 ┆ str │
╞═══════════╪═══════════════════╪════════╪════════════════════════╪═════╪═══════╪══════╪═════════╡
│ 426 ┆ null ┆ Start ┆ Emergency stop top box ┆ T02 ┆ 1 ┆ null ┆ null │
│ 427 ┆ null ┆ Start ┆ Safety chain open ┆ T02 ┆ 1 ┆ null ┆ null │
│ 428 ┆ null ┆ Start ┆ Emergency stop nacelle ┆ T02 ┆ 1 ┆ null ┆ null │
│ 786 ┆ null ┆ Start ┆ Emergency stop top box ┆ T03 ┆ 1 ┆ null ┆ null │
│ 1486 ┆ null ┆ Start ┆ Overload gear oil pump ┆ T06 ┆ 1 ┆ null ┆ null │
└───────────┴───────────────────┴────────┴────────────────────────┴─────┴───────┴──────┴─────────┘
Missing value counts per column:
shape: (1, 7)
┌───────────┬────────┬─────────┬─────┬───────┬──────┬─────────┐
│ Timestamp ┆ Status ┆ Message ┆ wt ┆ State ┆ Year ┆ wt_Year │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ u32 ┆ u32 ┆ u32 ┆ u32 ┆ u32 ┆ u32 ┆ u32 │
╞═══════════╪════════╪═════════╪═════╪═══════╪══════╪═════════╡
│ 5 ┆ 0 ┆ 0 ┆ 0 ┆ 0 ┆ 5 ┆ 5 │
└───────────┴────────┴─────────┴─────┴───────┴──────┴─────────┘
C:\Users\SAL9000\AppData\Local\Temp\ipykernel_20864\4106017410.py:3: DeprecationWarning:
`DataFrame.with_row_count` is deprecated. Use `with_row_index` instead. Note that the default column name has changed from 'row_nr' to 'index'.
Plot Square Signal
Show the code
```{python}
#| label: Square-Signal-Plot
import plotly.express as px
# Other line_shape options, or interpolation methods between given points:
# 'hv' step ends, equivalent to pyplot's post option;
# 'vh' step starts;
# 'hvh' step middles, x axis;
# 'vhv' step middles, y axis;
# 'spline' smooth curve between points;
# 'linear' line segments between points, default value for line_shape.
fig = px.line(tstates, x='Timestamp', y="State", line_shape='hv', facet_row="wt_Year", color="wt",
title="Time / State Diagram for Kellmarsh Wind Turbines (2016-2022)",
subtitle= "(0) Down Step=Forced outage (IEC), (1) Up Step=Start",
labels={"Timestamp": "Time (UTC)", "State": "", "wt": ""},facet_row_spacing=0.01 )
# Update y-axis to show only 0 and 1 as labels
fig.update_yaxes(
tickvals=[0, 1], # Set tick values to 0 and 1
ticktext=["", ""] # Optional: Explicitly set tick text
)
fig.update_xaxes(matches=None, tickformat="%b" )
# Update facet labels orientation and size
fig.for_each_annotation(lambda a: a.update(text=a.text.split("-")[-1],textangle=0, xanchor='left', x=-0.04)) # Make labels horizontal
fig.update_annotations(font_size=8) # Reduce font size
fig.show()
```